package com.rockcent.wechat.service;

import com.thoughtworks.xstream.XStream;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.BeanUtils;
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.pay.request.WxPayRefundRequest;
import me.chanjar.weixin.mp.bean.pay.result.WxPayBaseResult;
import me.chanjar.weixin.mp.bean.pay.result.WxPayRefundResult;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Created by DK on 17/2/22.
 */@Service
public class WxMpPayService {

    private static final Logger log = LoggerFactory.getLogger(WxMpPayService.class);

    private static final String PAY_BASE_URL = "https://api.mch.weixin.qq.com";
    private static final String[] TRADE_TYPES = new String[]{"JSAPI","NATIVE", "APP"};
    private static final String[] REFUND_ACCOUNT =  new String[]{"REFUND_SOURCE_RECHARGE_FUNDS",
            "REFUND_SOURCE_UNSETTLED_FUNDS"};

    public WxPayRefundResult refund(WxPayRefundRequest request, InputStream  keyFileInputStream, WxMpService wxMpService) throws WxErrorException {
        checkParameters(request);

        XStream xstream = XStreamInitializer.getInstance();
        xstream.processAnnotations(WxPayRefundRequest.class);
        xstream.processAnnotations(WxPayRefundResult.class);

        request.setAppid(wxMpService.getWxMpConfigStorage().getAppId());
        String partnerId = wxMpService.getWxMpConfigStorage().getPartnerId();
        request.setMchId(partnerId);
        request.setNonceStr( System.currentTimeMillis() + "");
        request.setOpUserId(partnerId);
        String sign = this.createSign(BeanUtils.xmlBean2Map(request), wxMpService.getWxMpConfigStorage().getPartnerKey());
        request.setSign(sign);

        String url = PAY_BASE_URL + "/secapi/pay/refund";
        String responseContent = this.executeRequestWithKeyFile(url, keyFileInputStream, xstream.toXML(request), partnerId, wxMpService);
        WxPayRefundResult result = (WxPayRefundResult) xstream.fromXML(responseContent);
        this.checkResult(result);
        return result;
    }


    private String executeRequestWithKeyFile( String url, InputStream keyFileInputStream, String requestStr, String mchId, WxMpService wxMpService) throws WxErrorException {
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(keyFileInputStream, mchId.toCharArray());

            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
                    new DefaultHostnameVerifier());

            HttpPost httpPost = new HttpPost(url);
            if (wxMpService.getHttpProxy() != null) {
                httpPost.setConfig(RequestConfig.custom().setProxy(wxMpService.getHttpProxy()).build());
            }

            try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build()) {
                httpPost.setEntity(new StringEntity(new String(requestStr.getBytes("UTF-8"), "ISO-8859-1")));
                try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
                    String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
                    this.log.debug("\n[URL]:  {}\n[PARAMS]: {}\n[RESPONSE]: {}",url, requestStr, result);
                    return result;
                }
            }finally {
                httpPost.releaseConnection();
            }
        } catch (Exception e) {
            this.log.warn("======================>\n[URL]:  {}\n[PARAMS]: {}\n[EXCEPTION]: {}", url, requestStr, e.getMessage());
            throw new WxErrorException(WxError.newBuilder().setErrorCode(-1).setErrorMsg(e.getMessage()).build(), e);
        }
    }

    private void checkResult(WxPayBaseResult result) throws WxErrorException {
        if (!"SUCCESS".equalsIgnoreCase(result.getReturnCode())
                || !"SUCCESS".equalsIgnoreCase(result.getResultCode())) {
            throw new WxErrorException(WxError.newBuilder().setErrorCode(-1)
                    .setErrorMsg("返回代码:" + result.getReturnCode() + ", 返回信息: "
                            + result.getReturnMsg() + ", 结果代码: " + result.getResultCode() + ", 错误代码: "
                            + result.getErrCode() + ", 错误详情: " + result.getErrCodeDes())
                    .build());
        }
    }

    private void checkParameters(WxPayRefundRequest request) throws WxErrorException {
        BeanUtils.checkRequiredFields(request);

        if (StringUtils.isNotBlank(request.getRefundAccount())) {
            if(!ArrayUtils.contains(REFUND_ACCOUNT, request.getRefundAccount())){
                throw new IllegalArgumentException("refund_account目前必须为" + Arrays.toString(REFUND_ACCOUNT) + "其中之一");
            }
        }

        if (StringUtils.isBlank(request.getOutTradeNo()) && StringUtils.isBlank(request.getTransactionId())) {
            throw new IllegalArgumentException("transaction_id 和 out_trade_no 不能同时为空，必须提供一个");
        }
    }

    private String createSign(Map<String, String> packageParams, String signKey) {
        SortedMap<String, String> sortedMap = new TreeMap<>(packageParams);

        StringBuffer toSign = new StringBuffer();
        for (String key : sortedMap.keySet()) {
            String value = packageParams.get(key);
            if (null != value && !"".equals(value) && !"sign".equals(key)
                    && !"key".equals(key)) {
                toSign.append(key + "=" + value + "&");
            }
        }

        toSign.append("key=" + signKey);

        return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
    }
}
